using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics.Contracts;
using Konst3d.Efficiency.IO;

namespace Konst3d.Efficiency.Suite.SparsedBinary
{
    internal class StatisticYear : IDisposable, IStatisticDayStorage
    {
        #region Types

        private struct DayEntry
        {
            public byte Active;
            public int CommentaryID;
            public float Value;
        }

        #endregion // Types

        #region Constants

        private const int DAYS_IN_MONTH = 31;
        private const int MONTH_IN_YEAR = 12;
        private const int DAYS_IN_YEAR = DAYS_IN_MONTH * MONTH_IN_YEAR;
        private const int DAY_RECORD_BYTE_SIZE = 9;
        private const int RECORD_ACTIVE_OFFSET = 0;
        private const int RECORD_COMMENTARY_OFFSET = 1;
        private const int RECORD_VALUE_OFFSET = 5;

        #endregion // Constants

        #region Properties

        public bool IsLoaded
        {
            get;
            private set;
        }

        #endregion // Properties

        #region Constructor/Finalizer

        internal StatisticYear(int year, BinaryReader reader, BinaryWriter writer, long offset, LabelsStorage labels)
        {
            Contract.Requires(reader != null);
            Contract.Requires(writer != null);
            Contract.Requires(labels != null);

            _reader = reader;
            _writer = writer;
            _year = year;
            _offset = offset;
            _labels = labels;
        }

        ~StatisticYear()
        {
            Dispose(false);
        }

        #endregion // Constructor/Finalizer

        #region Public Methods

        public void Load()
        {
            if (IsLoaded)
            {
                UnLoad();
            }

            DateTime start = new DateTime(_year, 1, 1);
            DateTime end = new DateTime(_year, 12, 31);

            for (DateTime date = start; date <= end; date = date.AddDays(1))
            {
                var entry = LoadEntry(date);

                _days[GetDayIndex(date)] = new StatisticDay(this, date, entry.Active != 0, entry.Value, _labels.GetLabel(entry.CommentaryID));
            }

            IsLoaded = true;
        }

        public void UnLoad()
        {
            if (IsLoaded)
            {
                for (int i = 0; i < _days.Length; ++i)
                {
                    _days[i] = null;
                }

                IsLoaded = false;
            }
        }

        public StatisticDay GetDay(DateTime date)
        {
            Contract.Assert(ContainsDay(date));

            if (!IsLoaded)
            {
                Load();
            }

            return _days[GetDayIndex(date)];
        }

        #endregion // Public Methods

        #region IStatisticDayStorage Members

        void IStatisticDayStorage.SetDayActive(DateTime date, bool active)
        {
            Contract.Assert(!_disposed);
            Contract.Assert(ContainsDay(date));

            _writer.Write(active ? (byte)1 : (byte)0, (int)GetDayOffset(date) + RECORD_ACTIVE_OFFSET);
            _writer.Flush();
        }

        void IStatisticDayStorage.SetDayCommentary(DateTime date, string commentary)
        {
            Contract.Assert(!_disposed);
            Contract.Assert(ContainsDay(date));

            int commentaryID = _reader.ReadInt32(GetDayOffset(date) + RECORD_COMMENTARY_OFFSET);
            commentaryID = _labels.UpdateLabel(commentaryID, commentary);

            _writer.Write(commentaryID, (int)GetDayOffset(date) + RECORD_COMMENTARY_OFFSET);
            _writer.Flush();
        }

        void IStatisticDayStorage.SetDayValue(DateTime date, float value)
        {
            Contract.Assert(!_disposed);
            Contract.Assert(ContainsDay(date));

            _writer.Write(value, (int)GetDayOffset(date) + RECORD_VALUE_OFFSET);
            _writer.Flush();
        }

        #endregion // IStatisticDayStorage Members

        #region IDisposable Members

        public void Dispose()
        {
            GC.SuppressFinalize(this);
            Dispose(true);
        }

        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    UnLoad();
                }

                _disposed = true;
            }
        }

        #endregion

        #region Private Methods

        private DayEntry LoadEntry(DateTime date)
        {
            DayEntry entry = new DayEntry();

            entry.Active = _reader.ReadByte(GetDayOffset(date));
            entry.CommentaryID = _reader.ReadInt32();
            entry.Value = _reader.ReadSingle();

            return entry;
        }

        private bool ContainsDay(DateTime date)
        {
            return date.Year == _year;
        }

        private long GetDayOffset(DateTime date)
        {
            return _offset + ((date.Month - 1) * DAYS_IN_MONTH + date.Day - 1) * DAY_RECORD_BYTE_SIZE;
        }

        private int GetDayIndex(DateTime date)
        {
            return (date.Month - 1) * DAYS_IN_MONTH + date.Day - 1;
        }

        #endregion // Private Methods

        #region Fields

        private bool _disposed = false;
        private int _year;
        private BinaryReader _reader;
        private BinaryWriter _writer;
        private long _offset;
        private LabelsStorage _labels;
        private StatisticDay[] _days = new StatisticDay[MONTH_IN_YEAR * DAYS_IN_MONTH];

        #endregion // Fields
    }
}
